/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package gametheory;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import javax.imageio.ImageIO;

/**
 *
 * @author 3mara
 */
public class Test {

    /**
     * @param args the command line arguments
     */
    public static int width = 500, height = 500;
    static ArrayList<Camera> cameras = new ArrayList<Camera>();
    static ArrayList<Integer> cameraList = new ArrayList<Integer>();
    static ArrayList<Integer> personList = new ArrayList<Integer>();
    static ArrayList<Person> persons = new ArrayList<Person>();
    static ArrayList<Rectangle> obstacles = new ArrayList<Rectangle>();
    static double delta_angle = 0, number_of_orientations;
    static int d2, d3;
    static int[] isAvailable;
    static String[] cameraAssign;
    static double[] screenRatio;
    static double[] delta_pan;
    static double[] cameraPperson;
    static double[][] diffCamera;
    static double[] zooms = {0.25, 0.166667, 0.111111111111, 0.083333333333333};

    public static int[] getMax(int[][][] cameras) {
        int[] index = new int[cameras.length];
        d2 = cameras[0].length;
        d3 = cameras[0][0].length;
        for (int i = 0; i < cameras.length; i++) {
            int temp = -1;
            index[i] = (i * d2 * d3);
            for (int j = 0; j < cameras[i].length; j++) {
                for (int k = 0; k < cameras[i][j].length; k++) {
                    if (temp < cameras[i][j][k] && cameras[i][j][k] > 0) {
                        temp = cameras[i][j][k];
                        index[i] = (i * d2 * d3) + (j * d3) + k;
                    }
                }
            }
        }
        return index;
    }

    public static ArrayList<AssignmentPair> sort(ArrayList<AssignmentPair> list) {
        AssignmentPair[] temp = new AssignmentPair[list.size()];
        for (int i = 0; i < list.size(); i++) {
            temp[i] = list.get(i);
        }
        Arrays.sort(temp);
        ArrayList<AssignmentPair> result = new ArrayList<AssignmentPair>();
        for (int i = 0; i < list.size(); i++) {
            result.add(temp[i]);
        }
        return result;
    }

    public static int[] getAssignment(boolean[][][][] spotted, double[][] probMatrix, int[] orientAssign, int[] zoomAssign, int d1, int d2, int d3) {
        int[] assignment = new int[probMatrix[0].length];
        for (int i = 0; i < assignment.length; i++) {
            assignment[i] = -1;
        }
        ArrayList<ArrayList<AssignmentPair>> persons = new ArrayList<ArrayList<AssignmentPair>>();

        for (int per = 0; per < probMatrix[per].length; per++) {
            persons.add(new ArrayList<AssignmentPair>());
            for (int camera = 0; camera < probMatrix.length; camera++) {
                persons.get(per).add(new AssignmentPair(camera, probMatrix[camera][per]));
            }
            persons.set(per, sort(persons.get(per)));
        }
        //======================================================================
        isAvailable = new int[d1];
        for (int b = 0; b < isAvailable.length; b++) {
            isAvailable[b] = -1;
        }
        for (int index = 0; index < probMatrix.length && isAvailable(isAvailable); index++) {
            int[][][] camera = new int[d1][d2][d3];
            for (int i = 0; i < persons.size(); i++) {
                if (persons.get(i) != null) {
                    int ind = persons.get(i).get(index).cameraID;
                    if (persons.get(i).get(index).utility > 0) {
                        camera[ind / (d2 * d3)][(ind / d3) % d2][ind % d3]++;
                    }
                }
            }
            int[] maxIndex = getMax(camera);
            for (int camIndex = 0; camIndex < maxIndex.length; camIndex++) {
                if (maxIndex[camIndex] != -1) {
                    for (int i = 0; i < persons.size(); i++) {
                        if (persons.get(i) != null && persons.get(i).get(index).cameraID == maxIndex[camIndex] && isPossible(maxIndex[camIndex])) {
//                            assignment[i] = (maxIndex[camIndex] / (d2 * d3));
                            cameraAssign[(maxIndex[camIndex] / (d2 * d3))] = "" + ((maxIndex[camIndex]) / (d3)) % d2 + "," + (maxIndex[camIndex]) % (d3);
                            assignment[i] = (maxIndex[camIndex]);
                            screenRatio[i] = getRatio(cameras.get(maxIndex[camIndex]), Test.persons.get(i), ((maxIndex[camIndex]) / (d3)) % d2);
                            zoomAssign[i] = ((maxIndex[camIndex]) / (d3)) % d2;
                            orientAssign[i] = (maxIndex[camIndex]) % (d3);
                            persons.set(i, null);
                            isAvailable[maxIndex[camIndex] / (d2 * d3)] = maxIndex[camIndex];
                        }
                    }
                }
            }
        }
        //======================================================================

        for (int per = 0; per < assignment.length; per++) {
//            System.out.println("Person: " + per + " Camera: " + assignment[per] + " Zoom: " + zoomAssign[per] + " Orient.: " + orientAssign[per]);
        }
//        System.out.println("DONE");
        return assignment;
    }

    public static double getRatio(Camera camera, Person person, int zoom) {
        double threshold = 0.5;
        double nearZRadius = 0.5;
        double temp = nearZRadius * 10 / camera.point.distance(person.point);
//        System.out.println(temp);
        double totalnearZ = zooms[zoom] * Math.PI * nearZRadius;
        temp /= totalnearZ;
        return temp;
    }

    public static boolean isAvailable(int[] array) {
        for (int i = 0; i < array.length; i++) {
            if (array[i] == -1) {
                return true;
            }
        }
        return false;
    }

    public static boolean isPossible(int camera) {
        if (isAvailable[camera / (d2 * d3)] == camera || isAvailable[camera / (d2 * d3)] == -1) {
            return true;
        }
        return false;
    }

    public static void paint(boolean[][][][] spotted, int[] assign, int[] orient, int[] zoom, int mapIndex) throws IOException {
        delta_angle = 2 * Math.PI / number_of_orientations;
        int[] zoomRanges0 = {505, 300, 500, 500};
        int[] zoomRanges1 = {505, 173, 210, 135};
        BufferedImage viz = new BufferedImage(Test.width,
                Test.height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D _g = (Graphics2D) viz.getGraphics();
        int i = 0;
        for (Camera cam : cameras) {
            _g.setColor(Color.BLACK);
            _g.drawString("cam", (float) cam.point.x, (float) cam.point.y);
            _g.setColor(Color.GREEN);
            i++;
            _g.fill(new Rectangle2D.Double(cam.point.x - 5, cam.point.y - 5, 10, 10));
        }
        // draw people
        i = 0;
        for (Person man : persons) {
            _g.setColor(Color.RED);
            _g.fill(new Rectangle2D.Double(man.point.x - 5, man.point.y - 5, 10, 10));
            i++;
        }

        for (int per = 0; per < assign.length; per++) {
            if (assign[per] != -1) {
                if (spottedFunction(spotted, assign[per], orient[per], zoom[per], per)) {
                    Camera cam = cameras.get(assign[per]);
                    Point2D.Double[] pArr = {new Point2D.Double(cam.point.x, cam.point.y),
                        new Point2D.Double(cam.point.x + 710, cam.point.y),
                        new Point2D.Double(cam.point.x + zoomRanges0[zoom[per]], cam.point.y + zoomRanges1[zoom[per]])
                    };
                    _g.setStroke(new BasicStroke(1));
                    _g.setColor(Color.GREEN);
                    _g.draw(new Line2D.Float(cam.point, persons.get(per).point));
                    _g.setColor(Color.BLUE);
                    _g.fill(new Rectangle2D.Float((int) persons.get(per).point.x - 5,
                            (int) persons.get(per).point.y - 5, 10, 10));
                }
            }
        }

        for (int camera = 0; camera < cameraAssign.length; camera++) {
            if (cameraAssign[camera] != null && !cameraAssign[camera].equals("")) {
                String[] temp = cameraAssign[camera].split(",");
                int camZoom = Integer.parseInt(temp[0]);
                int camOrient = Integer.parseInt(temp[1]);
                delta_pan[camera] = Math.abs(number_of_orientations / 2 - camOrient);
                int index = (camera * d2 * d3) + (camZoom * d3) + camOrient;
                Camera cam = cameras.get(index);
                for (int p = 0; p < spotted[camera][camZoom][camOrient].length; p++) {
                    if (spotted[camera][camZoom][camOrient][p]) {
                        screenRatio[p] = getRatio(cameras.get(camera), Test.persons.get(p), camZoom);
                        _g.setStroke(new BasicStroke(1));
                        _g.setColor(Color.GREEN);
                        _g.draw(new Line2D.Float(cam.point, persons.get(p).point));
                        _g.setColor(Color.BLUE);
                        _g.fill(new Rectangle2D.Float((int) persons.get(p).point.x - 5,
                                (int) persons.get(p).point.y - 5, 10, 10));
                    }
                }
            }
        }
        String OUTPUT_DIR = "output_Z/";
        new File(OUTPUT_DIR).mkdirs();
        File outputfile = new File(OUTPUT_DIR + mapIndex + "_" + 0 + "_" + Arrays.toString(assign) + ".png");
        ImageIO.write(viz, "png", outputfile);
    }

    public static boolean spottedFunction(boolean[][][][] spotted, int cam, int orient, int zoom, int p) {
        return spotted[cam / (spotted[0].length * spotted[0][0].length)][zoom][orient][p];
    }

    public static void main(String[] args) throws IOException {
//        int numpers[] = {10, 20, 30, 40, 50};
//        int mindist[] = {5, 8, 12, 17, 20};
        int numpers[] = {10, 15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 100};
        int mindist[] = {5, 7, 8, 12, 12, 17, 20, 24, 30, 34, 37, 40};
//        int numpers[] = {30};
//        int mindist[] = {12};
        double taw = 1;
        double threshold = 3000;
        ArrayList<Criteria> criteriaList = new ArrayList<Criteria>();
        criteriaList.add(new CrtCoverage());
        criteriaList.add(new CrtZoom());
        criteriaList.add(new CrtPan());
//        criteriaList.add(new CrtAngle());
        for (int k = 0; k < 5; k++) {
            int numPersons = numpers[k];
            int minDistance = mindist[k];
            File folder = new File("assets/Scenes/maps_" + numPersons + "_" + minDistance);
            File[] listOfFiles = folder.listFiles();
            listOfFiles = folder.listFiles();
            double cov = 0;
            double avgPan = 0;
            double avgRatio = 0;
            double avgTime = 0;
            double camPersonAvg = 0;
            double k_angleAvg = 0;
            for (int mapIndex = 1; mapIndex < listOfFiles.length; mapIndex++) {
                AngularSweep as = new AngularSweep(listOfFiles[mapIndex].getAbsolutePath());
                number_of_orientations = as.number_of_men;
                cameras = new ArrayList<Camera>();
                cameraList = new ArrayList<Integer>();
                personList = new ArrayList<Integer>();
                persons = new ArrayList<Person>();
                cameraAssign = new String[as.number_of_cameras];
                screenRatio = new double[as.number_of_men];
                cameraPperson = new double[as.number_of_men];
                delta_pan = new double[as.number_of_cameras];
                diffCamera = new double[as.number_of_men][3];
                double[][] probMatrix = new double[as.number_of_cameras * as.number_of_zooms * as.number_of_orientations][as.number_of_men];
                double[][] expectedUtilityMatrix = new double[as.number_of_cameras * as.number_of_zooms * as.number_of_orientations][as.number_of_men];
                double[][] utilityMatrix = new double[as.number_of_cameras * as.number_of_zooms * as.number_of_orientations][as.number_of_men];
                //=========================Initial Start========================
                long start = System.currentTimeMillis();
                as.sweep();
                //=========================Start Extraction=====================
                int index = 0;
                int camIndex = 0;
                for (int i = 0; i < as.spotted.length; i++) {
                    for (int j = 0; j < as.spotted[i].length; j++) {
                        for (int m = 0; m < as.spotted[i][j].length; m++) {
                            Camera temp;
                            if (!cameraList.contains(camIndex)) {
                                temp = new Camera(as.cameras.get(i), j, m);
                                temp.criteriaList = criteriaList;
                                cameras.add(camIndex, temp);
                                cameraList.add(camIndex);
                                camIndex++;
                            } else {
                                temp = cameras.get(cameraList.indexOf(camIndex));
                            }
                            for (int l = 0; l < as.spotted[i][j][m].length; l++) {
                                Person per;
                                if (!personList.contains(l)) {
                                    per = new Person(as.people.get(l), temp);
                                    per.criteriaList = criteriaList;
                                    personList.add(l);
                                    persons.add(per);
                                } else {
                                    per = persons.get(personList.indexOf(l));
                                }
                                if (as.spotted[i][j][m][l]) {
                                    temp.assignedPersons.add(per);
                                    per.cameraList.add(temp);
                                    probMatrix[index][l] = 1;
                                    expectedUtilityMatrix[index][l] = 0.01;
                                } else {
                                    probMatrix[index][l] = 0;
                                    expectedUtilityMatrix[index][l] = 0;
                                }
                            }
                            index++;
                        }
                    }
                }
                //=========================DONE  Extraction=====================
                PrintWriter out_prob = new PrintWriter(new FileWriter("log.txt"));
                PrintWriter out_uti = new PrintWriter(new FileWriter("utility.txt"));
                double prevGlobalUtility = 0;
                int iter = 0;
                boolean cont = true;
                while (iter < 10 && cont) {
                    iter++;
                    for (int i = 0; i < probMatrix.length; i++) {
                        for (int j = 0; j < probMatrix[i].length; j++) {
                            double numerator = Math.exp((1 / taw) * expectedUtilityMatrix[i][j]);
                            double denimenator = 0;
                            for (int m = 0; m < probMatrix.length; m++) {
                                denimenator += Math.exp((1 / taw) * expectedUtilityMatrix[m][j]);
                            }
                            probMatrix[i][j] = numerator / denimenator;
                            //set expected Utility
                            utilityMatrix[i][j] = persons.get(personList.indexOf(j)).getUtility();
                            expectedUtilityMatrix[i][j] = persons.get(personList.indexOf(j)).getExpectedUtility(cameras.get(i), probMatrix[i][j]);
                            double temp = getGlobalUtility();
                            if (Math.abs(temp - prevGlobalUtility) < threshold) {
                                cont = false;
                                break;
                            }
                            prevGlobalUtility = temp;
                            out_prob.write(probMatrix[i][j] + ", ");
                            out_uti.write(expectedUtilityMatrix[i][j] + ", ");
                            out_prob.write("\n");
                            out_uti.write("\n");
                            out_prob.write("================== Number of Iteration: " + iter + " ==============\n");
                            out_uti.write("================== Number of Iteration: " + iter + " ==============\n");
                        }

                    }
                }
                out_prob.close();
                out_uti.close();
                int[] zoomAssign = new int[as.number_of_men];
                int[] orientAssign = new int[as.number_of_men];
                int[] assign = getAssignment(as.spotted, probMatrix, orientAssign, zoomAssign, as.number_of_cameras, as.number_of_zooms, as.number_of_orientations);
                long end = System.currentTimeMillis();
//                k_angleAvg += k_diff_camera(as, orientAssign);
//                camPersonAvg += cameraPerson(as.spotted);
                avgTime += end - start;
//                paint(as.spotted, assign, orientAssign, zoomAssign, mapIndex);
//                cov += coverage(as.spotted);
//                avgPan += delta_Pan();
//                avgRatio += getAvgRatio();


            }
//            System.out.println(cov / listOfFiles.length);
//            System.out.println(avgRatio / listOfFiles.length);
//            System.out.println(avgPan / listOfFiles.length);
            System.out.println(avgTime / listOfFiles.length);
//            System.out.println(camPersonAvg / listOfFiles.length);
//            System.out.println(k_angleAvg / listOfFiles.length);
//            System.out.println("============");
        }
    }

    public static double getGlobalUtility() {
        double result = 0;
        for (int i = 0; i < cameras.size(); i++) {
            result += cameras.get(i).getUtility();
        }
        return result;
    }

    public static double k_diff_camera(AngularSweep as, int[] orientAssign) {
        for (int i = 0; i < as.spotted.length; i++) {
            if (cameraAssign[i] != null && !cameraAssign[i].equals("")) {
                String[] array = cameraAssign[i].split(",");
                int camOrient = Integer.parseInt(array[1]);
                int camZoom = Integer.parseInt(array[0]);
                for (int per = 0; per < as.spotted[i][camZoom][camOrient].length; per++) {
                    if (as.spotted[i][camZoom][camOrient][per]) {
                        double angle = Geometry.computeAngle(
                                new double[]{as.people.get(per).x, as.people.get(per).y, 0},
                                new double[]{as.cameras.get(i).x, as.cameras.get(i).y, 0},
                                new double[]{640, as.people.get(per).y, 0});
                        angle = Math.ceil(angle * 360 / Math.PI);
                        if (angle > 120) {
                            if (diffCamera[per][0] != 1) {
                                diffCamera[per][0] = 1;
                            }
                        } else if (angle > 240) {
                            if (diffCamera[per][0] != 1) {
                                diffCamera[per][1] = 1;
                            }
                        } else {
                            if (diffCamera[per][0] != 1) {
                                diffCamera[per][2] = 1;
                            }
                        }
                    }
                }
            }
        }

        double result = 0;
        for (int i = 0; i < diffCamera.length; i++) {
            for (int j = 0; j < diffCamera[i].length; j++) {
                result += diffCamera[i][j];
            }
        }
        result /= diffCamera[0].length;
        result /= as.number_of_men;
        return result;
    }

    static public Point2D.Double lineRectIntersection(Line2D.Float r,
            Rectangle rect) {
        Point2D.Double p = null, x = null, y;
        PathIterator pit = rect.getPathIterator(null);
        float[] a = new float[6];
        while (!pit.isDone()) {
            switch (pit.currentSegment(a)) {
                case PathIterator.SEG_MOVETO:
                    x = new Point2D.Double(a[0], a[1]);
                    break;
                case PathIterator.SEG_LINETO:
                    y = new Point2D.Double(a[0], a[1]);
                    Point2D.Double q = lineLineIntersection(r, new Line2D.Float(x,
                            y));
                    if (q != null && (p == null || r.getP1().distance(q) < r.getP1().distance(p))) {
                        p = q;
                    }
                    x = y;
                    break;
            }
            pit.next();
        }
        return p;
    }

    static public Point2D.Double lineLineIntersection(Line2D.Float a,
            Line2D.Float b) {
        if (!a.intersectsLine(b)) {
            return null;
        }
        float delta = (a.x1 - a.x2) * (b.y1 - b.y2) - (a.y1 - a.y2) * (b.x1 - b.x2);
        float x = (a.x1 * a.y2 - a.y1 * a.x2) * (b.x1 - b.x2) - (b.x1 * b.y2 - b.y1 * b.x2) * (a.x1 - a.x2);
        float y = (a.x1 * a.y2 - a.y1 * a.x2) * (b.y1 - b.y2) - (b.x1 * b.y2 - b.y1 * b.x2) * (a.y1 - a.y2);
        return new Point2D.Double(x / delta, y / delta);
    }

    static public Line2D.Float clippedLine(Line2D.Float line,
            ArrayList<Rectangle> obstacles) {
        for (Rectangle obs : obstacles) { // loop over obstacles
            Point2D.Double p1 = lineRectIntersection(line, obs);
            if (p1 != null && line.getP1().distance(p1) < line.getP1().distance(
                    line.getP2())) {
                line = new Line2D.Float(line.getP1(), p1);
            }
        }
        return line;
    }

    public static double coverage(boolean[][][][] spotted) {
        double result = 0;
        boolean[] pers = new boolean[spotted[0][0][0].length];
        for (int i = 0; i < spotted.length; i++) {
            if (cameraAssign[i] != null && !cameraAssign[i].equals("")) {
                String[] array = cameraAssign[i].split(",");
                int camOrient = Integer.parseInt(array[1]);
                int camZoom = Integer.parseInt(array[0]);
                for (int p = 0; p < spotted[i][camZoom][camOrient].length; p++) {
                    if (spotted[i][camZoom][camOrient][p] && !pers[p]) {
                        pers[p] = true;
                        result++;
                    }
                }
            }
        }
        return result / spotted[0][0][0].length;
    }

    public static double cameraPerson(boolean[][][][] spotted) {
        double result = 0;
        boolean[] pers = new boolean[spotted[0][0][0].length];
        for (int i = 0; i < spotted.length; i++) {
            if (cameraAssign[i] != null && !cameraAssign[i].equals("")) {
                String[] array = cameraAssign[i].split(",");
                int camOrient = Integer.parseInt(array[1]);
                int camZoom = Integer.parseInt(array[0]);
                for (int p = 0; p < spotted[i][camZoom][camOrient].length; p++) {
                    if (spotted[i][camZoom][camOrient][p]) {
                        cameraPperson[p]++;
                    }
                }
            }
        }
        for (int i = 0; i < cameraPperson.length; i++) {
            result += cameraPperson[i];
        }
        return result / cameraPperson.length;
    }

    public static int delta_Pan() {
        int result = 0;
        for (int i = 0; i < delta_pan.length; i++) {
            result += delta_pan[i] * (360 / number_of_orientations);
        }
        return result / delta_pan.length;
    }

    public static double getAvgRatio() {
        double result = 0;
        for (int i = 0; i < screenRatio.length; i++) {
            result += screenRatio[i];
//            System.out.println(screenRatio[i]);
        }
        return result / screenRatio.length;
    }
}
